<script setup lang="ts">
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js'
import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js'
import { ref, onMounted } from 'vue'

const domEl = ref(null)

import textTureImg from '../images/checker.png'

const demoList = ref([
  { position: [0, 10, 20], controls: [0, 5, 0], lightName: '环境光' },
  { position: [0, 10, 20], controls: [0, 5, 0], lightName: '半球光' },
  { position: [0, 10, 20], controls: [0, 5, 0], lightName: '方向光' },
  { position: [0, 10, 20], controls: [0, 5, 0], lightName: '点光源' },
  { position: [0, 10, 20], controls: [0, 5, 0], lightName: '聚光灯' },
  { position: [0, 10, 20], controls: [0, 5, 0], lightName: '矩形区域光' },
])

enum LightsType {
  ENVIRONMENT_L = '环境光',
  HALFBALL_L = '半球光',
  DIRECTION_L = '方向光',
  POINT_L = '点光源',
  SPOT_L = '聚光灯',
  RECT_L = '矩形区域光'
}

const initLight = (domEl, demoItems = {}) => {

  function main() {

    const canvas = domEl
    const renderer = new THREE.WebGLRenderer( { antialias: true, canvas } )

    const camera = new THREE.PerspectiveCamera( 45, domEl.clientWidth/domEl.clientHeight, 0.1, 100 )
    camera.position.set( ...demoItems.position )

    const controls = new OrbitControls( camera, canvas )
    controls.target.set( ...demoItems.controls )

    const scene = new THREE.Scene()
    scene.background = new THREE.Color( 'black' )

    {

      const planeSize = 40

      const loader = new THREE.TextureLoader()
      const texture = loader.load( textTureImg )
      texture.wrapS = THREE.RepeatWrapping
      texture.wrapT = THREE.RepeatWrapping
      texture.magFilter = THREE.NearestFilter
      texture.colorSpace = THREE.SRGBColorSpace
      const repeats = planeSize / 2
      texture.repeat.set( repeats, repeats )

      const planeGeo = new THREE.PlaneGeometry( planeSize, planeSize )
      const planeMat = demoItems.lightName === LightsType.RECT_L ?
          new THREE.MeshStandardMaterial( {
            map: texture,
            side: THREE.DoubleSide,
          } ) :
          new THREE.MeshPhongMaterial( {
            map: texture,
            side: THREE.DoubleSide,
          } )
      const mesh = new THREE.Mesh( planeGeo, planeMat )
      mesh.rotation.x = Math.PI * - .5
      scene.add( mesh )

    }

    {

      const cubeSize = 4
      const cubeGeo = new THREE.BoxGeometry( cubeSize, cubeSize, cubeSize )
      const cubeMat = demoItems.lightName === LightsType.RECT_L ?
          new THREE.MeshStandardMaterial( { color: '#8AC' } ) :
          new THREE.MeshPhongMaterial( { color: '#8AC' } )
      const mesh = new THREE.Mesh( cubeGeo, cubeMat )
      mesh.position.set( cubeSize + 1, cubeSize / 2, 0 )
      scene.add( mesh )

    }

    {

      const sphereRadius = 3
      const sphereWidthDivisions = 32
      const sphereHeightDivisions = 16
      const sphereGeo = new THREE.SphereGeometry( sphereRadius, sphereWidthDivisions, sphereHeightDivisions )
      const sphereMat = demoItems.lightName === LightsType.RECT_L ?
          new THREE.MeshStandardMaterial( { color: '#CA8' } ) :
          new THREE.MeshPhongMaterial( { color: '#CA8' } )
      const mesh = new THREE.Mesh( sphereGeo, sphereMat )
      mesh.position.set( - sphereRadius - 1, sphereRadius + 2, 0 )
      scene.add( mesh )

    }

    class ColorGUIHelper {

      constructor( object, prop ) {

        this.object = object
        this.prop = prop

      }
      get value() {

        return `#${this.object[ this.prop ].getHexString()}`

      }
      set value( hexString ) {

        this.object[ this.prop ].set( hexString )

      }

    }

    const gui = new GUI({ container: domEl.parentNode, width: 310 })

    if(demoItems.lightName === LightsType.ENVIRONMENT_L) {
      const color = 0xFFFFFF
      const intensity = 1
      const light = new THREE.AmbientLight( color, intensity )
      scene.add( light )

      gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'color' )
      gui.add( light, 'intensity', 0, 5, 0.01 )
    }
    else if(demoItems.lightName === LightsType.HALFBALL_L) {
      const skyColor = 0xB1E1FF // light blue
      const groundColor = 0xB97A20 // brownish orange
      const intensity = 1
      const light = new THREE.HemisphereLight( skyColor, groundColor, intensity )
      scene.add( light )
      const helper = new THREE.HemisphereLightHelper( light, 3 )
      scene.add( helper )

      gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'skyColor' )
      gui.addColor( new ColorGUIHelper( light, 'groundColor' ), 'value' ).name( 'groundColor' )
      gui.add( light, 'intensity', 0, 5, 0.01 )
    }
    else if(demoItems.lightName === LightsType.DIRECTION_L) {
      const color = 0xFFFFFF
      const intensity = 1
      const light = new THREE.DirectionalLight( color, intensity )
      light.position.set( 0, 10, 0 )
      light.target.position.set( - 5, 0, 0 )
      scene.add( light )
      scene.add( light.target )

      gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'color' )
      gui.add( light, 'intensity', 0, 5, 0.01 )
      gui.add( light.target.position, 'x', - 10, 10, .01 )
      gui.add( light.target.position, 'z', - 10, 10, .01 )
      gui.add( light.target.position, 'y', 0, 10, .01 )

      const helper = new THREE.DirectionalLightHelper(light)
      scene.add(helper)

      const makeXYZGUI = (gui, vector3, name, onChangeFn) => {
        const folder = gui.addFolder(name)
        folder.add(vector3, 'x', -10, 10).onChange(onChangeFn)
        folder.add(vector3, 'y', 0, 10).onChange(onChangeFn)
        folder.add(vector3, 'z', -10, 10).onChange(onChangeFn)
        folder.open()
      }

      const updateLight = () => {
        light.target.updateMatrixWorld()
        helper.update()
      }
      updateLight()

      makeXYZGUI(gui, light.position, 'position', updateLight)
      makeXYZGUI(gui, light.target.position, 'target', updateLight)
    }
    else if(demoItems.lightName === LightsType.POINT_L) {
      const makeXYZGUI = ( gui, vector3, name, onChangeFn ) => {

        const folder = gui.addFolder( name )
        folder.add( vector3, 'x', - 10, 10 ).onChange( onChangeFn )
        folder.add( vector3, 'y', 0, 10 ).onChange( onChangeFn )
        folder.add( vector3, 'z', - 10, 10 ).onChange( onChangeFn )
        folder.open()

      }

      {

        const color = 0xFFFFFF
        const intensity = 150
        const light = new THREE.PointLight( color, intensity )
        light.position.set( 0, 10, 0 )
        scene.add( light )

        const helper = new THREE.PointLightHelper( light )
        scene.add( helper )

        const updateLight = () => {

          helper.update()

        }

        gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'color' )
        gui.add( light, 'intensity', 0, 250, 1 )
        gui.add( light, 'distance', 0, 40 ).onChange( updateLight )

        makeXYZGUI( gui, light.position, 'position' )

      }
    }
    else if(demoItems.lightName === LightsType.SPOT_L) {
      class DegRadHelper {

        constructor( obj, prop ) {

          this.obj = obj
          this.prop = prop

        }
        get value() {

          return THREE.MathUtils.radToDeg( this.obj[ this.prop ] )

        }
        set value( v ) {

          this.obj[ this.prop ] = THREE.MathUtils.degToRad( v )

        }

      }
      const makeXYZGUI = ( gui, vector3, name, onChangeFn ) => {

        const folder = gui.addFolder( name )
        folder.add( vector3, 'x', - 10, 10 ).onChange( onChangeFn )
        folder.add( vector3, 'y', 0, 10 ).onChange( onChangeFn )
        folder.add( vector3, 'z', - 10, 10 ).onChange( onChangeFn )
        folder.open()

      }

      {

        const color = 0xFFFFFF
        const intensity = 150
        const light = new THREE.SpotLight( color, intensity )
        light.position.set( 0, 10, 0 )
        scene.add( light )

        const helper = new THREE.SpotLightHelper( light )
        scene.add( helper )

        const updateLight = () => {
          light.target.updateMatrixWorld()
          helper.update()

        }

        gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'color' )
        gui.add( light, 'intensity', 0, 250, 1 )
        gui.add( light, 'distance', 0, 40 ).onChange( updateLight )
        gui.add( new DegRadHelper( light, 'angle' ), 'value', 0, 90 ).name( 'angle' ).onChange( updateLight )
        gui.add( light, 'penumbra', 0, 1, 0.01 )

        makeXYZGUI( gui, light.position, 'position', updateLight )
        makeXYZGUI( gui, light.target.position, 'target', updateLight )

      }
    }
    else if(demoItems.lightName === LightsType.RECT_L) {
      RectAreaLightUniformsLib.init()
      class DegRadHelper {

        constructor( obj, prop ) {

          this.obj = obj
          this.prop = prop

        }
        get value() {

          return THREE.MathUtils.radToDeg( this.obj[ this.prop ] )

        }
        set value( v ) {

          this.obj[ this.prop ] = THREE.MathUtils.degToRad( v )

        }

      }
      const makeXYZGUI = ( gui, vector3, name, onChangeFn ) => {

        const folder = gui.addFolder( name )
        folder.add( vector3, 'x', - 10, 10 ).onChange( onChangeFn )
        folder.add( vector3, 'y', 0, 10 ).onChange( onChangeFn )
        folder.add( vector3, 'z', - 10, 10 ).onChange( onChangeFn )
        folder.open()

      }

      {

        const color = 0xFFFFFF
        const intensity = 5
        const width = 12
        const height = 4
        const light = new THREE.RectAreaLight(color, intensity, width, height)
        light.position.set( 0, 10, 0 )
        light.rotation.x = THREE.MathUtils.degToRad(-90)
        scene.add( light )

        const helper = new RectAreaLightHelper(light)
        scene.add( helper )

        gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'color' )
        gui.add( light, 'intensity', 0, 10, 0.01 )
        gui.add( light, 'width', 0, 20 )
        gui.add( light, 'height', 0, 20 )
        gui.add( new DegRadHelper( light.rotation, 'x' ), 'value', - 180, 180 ).name( 'x rotation' )
        gui.add( new DegRadHelper( light.rotation, 'y' ), 'value', - 180, 180 ).name( 'y rotation' )
        gui.add( new DegRadHelper( light.rotation, 'z' ), 'value', - 180, 180 ).name( 'z rotation' )

        makeXYZGUI( gui, light.position, 'position' )

      }
    }


    function resizeRendererToDisplaySize( renderer ) {

      const canvas = renderer.domElement
      const width = canvas.clientWidth
      const height = canvas.clientHeight
      const needResize = canvas.width !== width || canvas.height !== height
      if ( needResize ) {

        renderer.setSize( width, height, false )

      }

      return needResize

    }

    function render() {

      if ( resizeRendererToDisplaySize( renderer ) ) {

        const canvas = renderer.domElement
        camera.aspect = canvas.clientWidth / canvas.clientHeight
        camera.updateProjectionMatrix()

      }

      renderer.render( scene, camera )

      requestAnimationFrame( render )

    }

    requestAnimationFrame( render )

  }

  main()
}

onMounted(() => {
  demoList.value.forEach((item, index) => {
    initLight(domEl.value[index], item)
  })
})


</script>

<template>
  <div class="domEl-class w-screen h-screen flex flex-wrap gap-2 overflow-y-auto">
    <div class="relative" v-for="(item, index) in demoList" :key="index">
      <canvas ref="domEl" class="w-full" style="height: calc(100% - 24px)"></canvas>
      <p class="text-center text-indigo-600">{{ item.lightName }}</p>
    </div>
  </div>
</template>

<style scoped lang="scss">
.domEl-class {
  width: calc(100% - 1px);
  >div {
    width: calc(50% - 0.5rem/2);
    height: calc(50% - 0.5rem/2);
  }
  :deep(.lil-gui.root) {
    position: absolute;
    right: 0;
    top: 0;
  }
}
</style>